#include "Map.h"

#include "FileUtils.h"
#include "Physics.h"

namespace GenBen {

#pragma pack(1)

// Page description as it appears in .bmap file
struct RawPage
{
	byte x, y;
	TileInfo layer[4][510];
	byte collision[510];
};

#pragma pack()

Map::Map(Graphics* graphics, string filename)
: camera(float(CAMERA_START_X), float(CAMERA_START_Y)), camera_speed(0.0f, 0.0f)
{
	ASSERT(graphics);
	ASSERT(sizeof(RawPage) == 4592);

	FILE* map_file = fopen(filename.c_str(), "rb");

	// Check file signature
	uint magic;
	fread(&magic, sizeof(uint), 1, map_file);
	ASSERT(magic == ('M' | ('A'<<8) | ('P'<<16) | (0<<24)));

	// Read tileset count
	byte tileset_count;
	fread(&tileset_count, 1, 1, map_file);
	this->tileset_count = tileset_count;
	tilesets = new Tileset*[tileset_count];

	// Read tileset file names, construct tileset objects
	for(uint i = 0; i < tileset_count; ++i)
	{
		string tileset_filename = readString(map_file);
		tilesets[i] = new Tileset(graphics, tileset_filename, VectorI(TILE_WIDTH, TILE_HEIGHT));
	}

	// Read page count
	uint16 page_count;
	fread(&page_count, sizeof(uint16), 1, map_file);
	this->page_count = page_count;
	pages = new Page*[page_count];

	// Read pages, convert to more convenient structure
	memset(page_map, 0, sizeof(page_map));
	RawPage page;
	for(uint i = 0; i < page_count; ++i)
	{
		fread(&page, sizeof(RawPage), 1, map_file);

		// Temp. Flip y coordinate
		page.y = 256 - page.y;

		// First and last columns must be empty
		ASSERT(page.x != 0 && page.x != 255 && page.y != 0 && page.y != 255);

		pages[i] = new Page(graphics, tilesets, tileset_count, page.x, page.y);
		page_map[page.x][page.y] = pages[i];

		// Fill tiles
		for(uint layer = 0; layer < LAYERS; ++layer)
			for(uint x = 0; x < PAGE_WIDTH; ++x)
				for(uint y = 0; y < PAGE_HEIGHT; ++y)
					pages[i]->tile_map.tilemap[layer][x][y] = page.layer[layer][y * PAGE_WIDTH + x];

		// Fill collision
		for(uint x = 0; x < PAGE_WIDTH; ++x)
			for(uint y = 0; y < PAGE_HEIGHT; ++y)
				pages[i]->collision_map[x][y] = page.collision[y * PAGE_WIDTH + x];
	}

	// Starting page can't be empty
	ASSERT(page_map[STARTING_PAGE_X][STARTING_PAGE_Y] != NULL);

	fclose(map_file);
}

Map::~Map()
{
	for(uint i = 0; i < page_count; ++i)
	{
		delete pages[i];
	}
	delete[] pages;

	for(uint i = 0; i < tileset_count; ++i)
	{
		delete tilesets[i];
	}
	delete[] tilesets;
}

Page* Map::getPage(byte x, byte y)
{
	return page_map[x][y];
}

void Map::moveCamera(const VectorF& new_pos, float time_scale)
{
	VectorF diff;
	diff.x = new_pos.x - camera.x;
	diff.y = new_pos.y - camera.y;

	camera_speed = camera_speed + diff;
	camera_speed.x *= 0.90f;
	camera_speed.y *= 0.90f;
	
	VectorF shift(camera_speed.x * time_scale, camera_speed.y * time_scale);

	static RectF env_rects[16];
	static int env_rect_count;
	env_rect_count = 0;

	bool two_pages = false;

	byte p1_x, p1_y, p2_x, p2_y;
	pageFromPoint(camera, p1_x, p1_y);
	pageFromPoint(camera + shift, p2_x, p2_y);

	if(p1_x != p2_x || p1_y != p2_y)
		two_pages = page_map[p2_x][p2_y] != NULL;

	if(page_map[p1_x+1][p1_y] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x+1, p1_y);
	if(page_map[p1_x-1][p1_y] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x-1, p1_y);
	if(page_map[p1_x][p1_y+1] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x, p1_y+1);
	if(page_map[p1_x][p1_y-1] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x, p1_y-1);

	if(page_map[p1_x+1][p1_y-1] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x+1, p1_y-1);
	if(page_map[p1_x-1][p1_y+1] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x-1, p1_y+1);
	if(page_map[p1_x+1][p1_y+1] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x+1, p1_y+1);
	if(page_map[p1_x-1][p1_y-1] == NULL)
		env_rects[env_rect_count++] = pageRect(p1_x-1, p1_y-1);

	// Optimize: remove redundant rects

	if(two_pages)
	{
		if(page_map[p2_x+1][p2_y] == NULL)
			env_rects[env_rect_count++] = pageRect(p2_x+1, p2_y);
		if(page_map[p2_x-1][p2_y] == NULL)
			env_rects[env_rect_count++] = pageRect(p2_x-1, p2_y);
		if(page_map[p2_x][p2_y+1] == NULL)
			env_rects[env_rect_count++] = pageRect(p2_x, p2_y+1);
		if(page_map[p2_x][p2_y-1] == NULL)
			env_rects[env_rect_count++] = pageRect(p2_x, p2_y-1);

		if(page_map[p2_x+1][p2_y-1] == NULL)
		env_rects[env_rect_count++] = pageRect(p2_x+1, p2_y-1);
		if(page_map[p2_x-1][p2_y+1] == NULL)
			env_rects[env_rect_count++] = pageRect(p2_x-1, p2_y+1);
		if(page_map[p2_x+1][p2_y+1] == NULL)
			env_rects[env_rect_count++] = pageRect(p2_x+1, p2_y+1);
		if(page_map[p2_x-1][p2_y-1] == NULL)
			env_rects[env_rect_count++] = pageRect(p2_x-1, p2_y-1);
	}

	RectF old_camera(camera.x - VIEWPORT_WIDTH/2, camera.y - VIEWPORT_HEIGHT/2, 
					 camera.x + VIEWPORT_WIDTH/2, camera.y + VIEWPORT_HEIGHT/2);
	RectF new_camera = multisampledSlidingCollision(old_camera, shift, env_rect_count, env_rects);

	camera.x = (new_camera.min.x + new_camera.max.x) / 2.0f;
	camera.y = (new_camera.min.y + new_camera.max.y) / 2.0f;
}

void Map::draw()
{
	RectF camera_rect(camera.x - VIEWPORT_WIDTH/2, camera.y - VIEWPORT_HEIGHT/2, 
				 camera.x + VIEWPORT_WIDTH/2, camera.y + VIEWPORT_HEIGHT/2);

	byte camera_page_x, camera_page_y;
	pageFromPoint(camera, camera_page_x, camera_page_y);

	drawPage(camera_page_x, camera_page_y);

	if(quickCollideRect(camera_rect, pageRect(camera_page_x+1, camera_page_y)))
		drawPage(camera_page_x+1, camera_page_y);
	if(quickCollideRect(camera_rect, pageRect(camera_page_x-1, camera_page_y)))
		drawPage(camera_page_x-1, camera_page_y);
	if(quickCollideRect(camera_rect, pageRect(camera_page_x, camera_page_y+1)))
		drawPage(camera_page_x, camera_page_y+1);
	if(quickCollideRect(camera_rect, pageRect(camera_page_x, camera_page_y-1)))
		drawPage(camera_page_x, camera_page_y-1);

	if(quickCollideRect(camera_rect, pageRect(camera_page_x+1, camera_page_y+1)))
		drawPage(camera_page_x+1, camera_page_y+1);
	if(quickCollideRect(camera_rect, pageRect(camera_page_x-1, camera_page_y+1)))
		drawPage(camera_page_x-1, camera_page_y+1);
	if(quickCollideRect(camera_rect, pageRect(camera_page_x+1, camera_page_y-1)))
		drawPage(camera_page_x+1, camera_page_y-1);
	if(quickCollideRect(camera_rect, pageRect(camera_page_x-1, camera_page_y-1)))
		drawPage(camera_page_x-1, camera_page_y-1);
}

void Map::drawPage(byte x, byte y)
{
	if(page_map[x][y] == NULL)
		return;

	RectF page_rect = pageRect(x, y);

	int left_x = int(page_rect.min.x) - (int(camera.x) - VIEWPORT_WIDTH/2);
	int top_y = int(page_rect.min.y) - (int(camera.y) - VIEWPORT_HEIGHT/2);
	
	VectorI screen_pos;
	screen_pos.x = left_x;
	screen_pos.y = top_y;
	page_map[x][y]->tile_map.drawLayer(0, PHYSICAL_L0_LAYER, screen_pos);
	page_map[x][y]->tile_map.drawLayer(1, PHYSICAL_L1_LAYER, screen_pos);
	page_map[x][y]->tile_map.drawLayer(2, PHYSICAL_L2_LAYER, screen_pos);
	page_map[x][y]->tile_map.drawLayer(3, PHYSICAL_L3_LAYER, screen_pos);
}

void Map::pageFromPoint(const VectorF& point, byte& x, byte &y)
{
	ASSERT(point.x >= 0 && point.y <= VIEWPORT_WIDTH*256);
	ASSERT(point.y >= 0 && point.y <= VIEWPORT_HEIGHT*256);

	x = byte(int(point.x) / VIEWPORT_WIDTH);
	y = byte(int(point.y) / VIEWPORT_HEIGHT);
}

RectF Map::pageRect(byte x, byte y)
{
	return RectF(float(x * VIEWPORT_WIDTH), float(y * VIEWPORT_HEIGHT), 
		float((x+1) * VIEWPORT_WIDTH), float((y+1) * VIEWPORT_HEIGHT));
}

} // namespace